//
//  TTReachability.m
//  Tint
//
//  Created by Kevin Wu on 8/22/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTReachability.h"




static void TTReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
{
	NSCAssert(info != NULL, @"info should not be NULL");
	NSCAssert([(NSObject *)info isKindOfClass:[TTReachability class]], @"info should be an instance of TTReachability");
    
	// We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively 
	// in case someone uses the Reachablity object in a different thread.
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
	TTReachability *reachability = (TTReachability *)info;
    
	// Post a notification to notify the client that the network status changed.
	[[NSNotificationCenter defaultCenter] postNotificationName:TTReachabilityStatusChangedNotification object:reachability];
	
	[pool release];
}




@interface TTReachability (Private)

- (TTNetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags;

@end

@implementation TTReachability (Private)

- (TTNetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags
{
    
	if ( (flags & kSCNetworkReachabilityFlagsReachable) == 0 ) {
		// if target host is not reachable
        return TTNetworkNotReachable;
	}
	
	if ( (flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0 ) {
		// if target host is reachable and no connection is required
		// then we'll assume (for now) that your on Wi-Fi
        return TTNetworkReachableViaWiFi;
	}
	
	
	if (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0) ||
         ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
    {
        // ... and the connection is on-demand (or on-traffic) if the
        //     calling application is using the CFSocketStream or higher APIs
        if ( (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0 ) {
            // ... and no [user] intervention is needed
            return TTNetworkReachableViaWiFi;
        }
    }
	
	if ( (flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN ) {
		// ... but WWAN connections are OK if the calling application
		//     is using the CFNetwork (CFSocketStream?) APIs.
        return TTNetworkReachableViaWWAN;
	}
    
    return TTNetworkNotReachable;
}

@end




@implementation TTReachability


#pragma mark - Memory

- (id)initWithNetworkReachability:(SCNetworkReachabilityRef)reachabilityRef
{
    self = [super init];
    if ( self ) {
        _reachabilityRef = reachabilityRef;
    }
    return self;
}

- (void)dealloc
{
    [self stopNotifier];
    TTReleaseCF(_reachabilityRef);
    [super dealloc];
}


#pragma mark - Public

static TTReachability *sharedReachability = nil;

+ (TTReachability *)sharedObject
{
    if ( sharedReachability == nil ) {
        struct sockaddr_in zeroAddress;
        bzero(&zeroAddress, sizeof(zeroAddress));
        zeroAddress.sin_len = sizeof(zeroAddress);
        zeroAddress.sin_family = AF_INET;
        SCNetworkReachabilityRef reachabilityRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress);
        if ( reachabilityRef ) {
            sharedReachability = [[self alloc] initWithNetworkReachability:reachabilityRef];
        }
    }
    return sharedReachability;
}

+ (BOOL)hasSharedObject
{
    return ( sharedReachability != nil );
}

+ (void)destroySharedObject
{
    if ( sharedReachability ) {
        TTRelease(sharedReachability);
    }
}


+ (TTReachability *)reachabilityWithHostName:(NSString *)hostName
{
    SCNetworkReachabilityRef reachabilityRef = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
	if ( reachabilityRef ) {
        return [[[self alloc] initWithNetworkReachability:reachabilityRef] autorelease]; // AUTO
	}
    return nil;
}

+ (TTReachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress
{
    SCNetworkReachabilityRef reachabilityRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)hostAddress);
    if ( reachabilityRef ) {
        return [[[self alloc] initWithNetworkReachability:reachabilityRef] autorelease]; // AUTO
	}
    return nil;
}

+ (TTReachability *)reachabilityForInternetConnection
{
    struct sockaddr_in zeroAddress;
	bzero(&zeroAddress, sizeof(zeroAddress));
	zeroAddress.sin_len = sizeof(zeroAddress);
	zeroAddress.sin_family = AF_INET;
	return [self reachabilityWithAddress:&zeroAddress]; // AUTO
}





- (BOOL)startNotifier
{
    
    NSAssert(_reachabilityRef != NULL, @"_reachabilityRef should not be NULL");
    
    SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};
    if ( SCNetworkReachabilitySetCallback(_reachabilityRef, TTReachabilityCallback, &context) ) {
        if ( SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode) ) {
            return YES;
        }
    }
	return NO;
}

- (void)stopNotifier
{
    NSAssert(_reachabilityRef != NULL, @"_reachabilityRef should not be NULL");
    
    if ( _reachabilityRef ) {
		SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
	}
}


- (TTNetworkStatus)networkStatus
{
    
    NSAssert(_reachabilityRef != NULL, @"_reachabilityRef should not be NULL");
    
	SCNetworkReachabilityFlags flags;
	if ( SCNetworkReachabilityGetFlags(_reachabilityRef, &flags) ) {
        return [self networkStatusForFlags:flags];
	}
    
	return TTNetworkNotReachable;
}





- (BOOL)connectionRequired
{
    
    NSAssert(_reachabilityRef != NULL, @"_reachabilityRef should not be NULL");
    
	SCNetworkReachabilityFlags flags = 0;
	if ( SCNetworkReachabilityGetFlags(_reachabilityRef, &flags) ) {
		return ( flags & kSCNetworkReachabilityFlagsConnectionRequired );
	}
	return NO;
}




- (BOOL)isNetworkReachable
{
    TTNetworkStatus status = [self networkStatus];
    return ((status == TTNetworkReachableViaWiFi) ||
            (status == TTNetworkReachableViaWWAN)
            );
}

- (BOOL)isNetworkReachableViaWiFi
{
    TTNetworkStatus status = [self networkStatus];
    return ( status == TTNetworkReachableViaWiFi );
}

- (BOOL)isNetworkReachableViaWWAN
{
    TTNetworkStatus status = [self networkStatus];
    return ( status == TTNetworkReachableViaWWAN );
}


@end
